package com.scalable.io.nio.basic.server;

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

/**
 * 读写事件处理器
 */
public class Handler implements Runnable {

    private final SelectionKey selectionKey;
    private final SocketChannel socketChannel;
    private final SocketAddress remoteAddress;
    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    private ByteBuffer sendBuffer = ByteBuffer.allocate(2048);

    private final static int READ = 0;
    private final static int SEND = 1;

    private int status = READ;

    public Handler(SocketChannel socketChannel, Selector selector) throws IOException {
        this.socketChannel = socketChannel; // 接收客户端连接
        this.socketChannel.configureBlocking(false);  // 置为非阻塞模式（selector仅允非阻塞模式）
        remoteAddress = socketChannel.getRemoteAddress();
        System.out.println("connection success " + remoteAddress);
        // 将该客户端注册到selector，得到一个SelectionKey，以后的select到的就绪动作全都是由该对象进行封装
        selectionKey = socketChannel.register(selector, 0);
        // 附加处理对象，当前是Handler对象，run是对象处理业务的方法
        selectionKey.attach(this);
        // 走到这里，说明之前Acceptor里的建连已完成，那么接下来就是读取动作，因此这里首先将读事件标记为“感兴趣”事件
        selectionKey.interestOps(SelectionKey.OP_READ);
        // 让阻塞的selector立即返回
        selector.wakeup();
    }

    @Override
    public void run() {
        try {
            switch (status) {
                case READ:
                    read();
                    break;
                case SEND:
                    send();
                    break;
                default:
            }
        } catch (IOException e) {
            // 这里的异常处理是做了汇总，
            // 常出的异常就是server端还有未读/写完的客户端消息，客户端就主动断开连接，
            // 这种情况下是不会触发返回-1的，这样下面read和write方法里的cancel和close就都无法触发，
            // 这样会导致死循环异常（read/write处理失败，事件又未被cancel，因此会不断的被select到，不断的报异常）
            System.err.println("read或send时发生异常！异常信息：" + e.getMessage());
            selectionKey.cancel();
            try {
                socketChannel.close();
            } catch (IOException e2) {
                System.err.println("关闭通道时发生异常！异常信息：" + e2.getMessage());
                e2.printStackTrace();
            }
        }
    }

    private void read() throws IOException {
        if (selectionKey.isValid()) {
            readBuffer.clear();
            // socketChannel -> readBuffer -> String
            int count = socketChannel.read(readBuffer);
            if (count > 0) {
                System.out.println("recv from " + remoteAddress + " : " + new String(readBuffer.array()));
                status = SEND;
                selectionKey.interestOps(SelectionKey.OP_WRITE);
            } else {
                selectionKey.cancel();
                System.out.println("connection close " + remoteAddress);
                socketChannel.close();
            }
        }
    }

    private void send() throws IOException {
        if (selectionKey.isValid()) {
            sendBuffer.clear();
            // String -> byte[] -> ByteBuffer
            sendBuffer.put("OK\n".getBytes());
            sendBuffer.flip();
            int count = socketChannel.write(sendBuffer);
            if (count < 0) {
                selectionKey.cancel();
                System.out.println("close connection " + remoteAddress);
                socketChannel.close();
            } else {
                status = READ;
                selectionKey.interestOps(SelectionKey.OP_READ);
            }
        }
    }
}
